/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 play_combat_flight.cpp

	$Header: /heroes4/play_combat_flight.cpp $

	$NoKeywords: $

 ************************************************************************/
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "play_combat_flight.h"

#include "angle.h"
#include "battlefield.h"
#include "combat_actor_action.h"
#include "combat_creature.h"
#include "combat_footprint.h"
#include "combat_object_model_cache.h"
#include "format_string.h"
#include "game_time.h"
#include "options.h"
#include "playing_sound.h"
#include "sound.h"
#include "stationary_combat_object.h"

// -----------------------------------------------------------------------
// fly to a location
// -----------------------------------------------------------------------
t_play_combat_flight::t_play_combat_flight( t_combat_creature&			creature, 
						                    t_map_point_3d const&		destination,
					                        t_combat_creature_handler	end_handler )
					: t_counted_animation( creature.get_battlefield() ), m_creature( creature ),
					  m_end_handler( end_handler ), m_destination( destination ),
					  t_counted_idle_processor( 20 )
{
	t_battlefield& battlefield = m_battlefield;

	// compute angle for movement
	t_direction    direction;
	int			   angle = get_angle( creature.get_position(), m_destination );

	direction = get_direction( angle );
	// turn and wait for one frame
	if (m_creature.has_action( k_combat_actor_action_base_frame ))
		battlefield.set_current_direction_and_action( m_creature,
													    direction,
														k_combat_actor_action_base_frame );
	else
		battlefield.set_current_direction_and_action( m_creature, direction,
														k_combat_actor_action_wait );
	battlefield.preload( m_creature, k_combat_actor_action_prewalk, direction );
	battlefield.preload( m_creature, k_combat_actor_action_walk, direction );
	battlefield.preload( m_creature, k_combat_actor_action_postwalk, direction );
	// start flying sound
	m_sound = m_creature.play_sound( k_combat_actor_action_walk, true );
	m_creature.clear_influence();
	set_delay( 100000 / (m_creature.get_frames_per_second()
					    * get_combat_animation_speed()));
}

t_play_combat_flight::~t_play_combat_flight()
{
}

// -----------------------------------------------------------------------
// fly to a location
// -----------------------------------------------------------------------
t_map_point_3d t_play_combat_flight::advance()
{
	// move forward
	t_map_point_3d result(0,0,0);

	m_sum += m_delta;
	if (m_sum.column >= m_threshold)
	{
		result.column = m_delta_sign.column * m_sum.column / m_threshold;
		m_sum.column = m_sum.column % m_threshold;
	}
	if (m_sum.row >= m_threshold)
	{
		result.row = m_delta_sign.row * m_sum.row / m_threshold;
		m_sum.row = m_sum.row % m_threshold;
	}
	if (m_sum.height >= m_threshold)
	{
		result.height = m_delta_sign.height * m_sum.height / m_threshold;
		m_sum.height = m_sum.height % m_threshold;
	}
	return result;
}

// -----------------------------------------------------------------------
// fly to a location
// -----------------------------------------------------------------------
void t_play_combat_flight::on_idle()
{
	t_uint32 current_time = get_time();
	t_uint32 next_time    = get_next_time() - get_delay();
	t_battlefield& battlefield = m_battlefield;

	while (elapsed_time( current_time, next_time ) >= 0)
	{
		t_combat_actor_action_id action = m_creature.get_current_action();
		int                      frame  = m_creature.get_current_frame_num();
		int                      frame_count = m_creature.get_frame_count();
		t_map_point_3d           position = m_creature.get_position();

		next_time += get_delay();
		set_next_time( next_time );
		// if we are not moving...
		if (action == k_combat_actor_action_wait 
			|| action == k_combat_actor_action_base_frame)
		{
			// and we have reached the destination, shut everything down.
			if (position == m_destination)
			{
				m_creature.set_shadow_displacement( 0, battlefield );
				m_creature.set_influence( );
				suspend_idle_processing();
				animation_ended();
				battlefield.check_gate_close();
				if (battlefield.get_tower( position >> k_battlefield_subcell_shift,
					                       m_creature.get_footprint()) != 0)
					m_creature.place_in_tower();
				assert( m_sound == 0 );
				m_end_handler( m_creature );
				return;
			}
			// otherwise, start preflight.
			battlefield.set_current_action( m_creature, k_combat_actor_action_prewalk );
			compute_next_waypoint();
			compute_distances( 0 );
			if (!m_creature.has_ability( k_ability_flying ))
			{
				std::string                 model_name;
				t_combat_object_model_cache cache;

				model_name = "flight.";
				model_name += format_string( "%i", m_creature.get_footprint_size() );
				cache = get_combat_object_model_cache( model_name, m_battlefield.get_model_scale() );
				m_creature.enable_shadow( false );
				m_spell_effect = new t_stationary_combat_object( battlefield, cache );
				battlefield.place_object( m_spell_effect,
											m_creature.get_position() + t_map_point_2d(1,1));
			}
			return;
		}
		// move forward
		position += advance();
		m_battlefield.move_object( m_creature, position );
		if (m_spell_effect != 0)
		{
			t_map_point_3d spell_position = m_creature.get_footprint_center() + t_map_point_2d(2,2);

			battlefield.move_object( *m_spell_effect, spell_position );
		}

		m_frames--;
		frame++;
		if (action == k_combat_actor_action_walk && frame == frame_count)
			frame = 0;
		if (frame < frame_count)
			battlefield.set_current_frame_num( m_creature, frame );
		// if we have finished the current segment of flight...
		if (m_frames == 0)
		{
			assert( position == m_waypoint );
			// if we landed, shift to wait.
			if ( position == m_destination )
			{
				if (m_creature.has_action( k_combat_actor_action_base_frame ))
					battlefield.set_current_action( m_creature, 
					                                  k_combat_actor_action_base_frame );
				else
					battlefield.set_current_action( m_creature, 
					                                  k_combat_actor_action_wait );
				if (m_spell_effect != 0)
					battlefield.remove_object( m_spell_effect );
				m_creature.enable_shadow();
				if (m_sound != 0)
				{
					m_sound->stop();
					m_sound = 0;
				}
			}
			else
			{	// otherwise, compute next segment.
				compute_next_waypoint();
				compute_distances( frame );
			}
			set_delay( 100000 / (m_creature.get_frames_per_second() 
				       * get_combat_animation_speed()));
		}
	}
}


// -----------------------------------------------------------------------
// fly to a location
// -----------------------------------------------------------------------
void t_play_combat_flight::compute_next_waypoint()
{
	t_map_point_2d				point;
	std::vector<t_map_point_3d> points;
	t_map_point_3d				sum(0,0,0);
	int							i;
	int							height;
	int							distance;
	t_abstract_combat_object*	obstacle = 0;
	t_abstract_combat_object*	last_obstacle = 0;
	int							obstacle_start;
	t_map_point_3d				obstacle_center;
	t_map_point_3d				position = m_creature.get_position();
	t_map_point_3d              start = position;
	int                         waypoint_position;

	// compute a straight line from here to the destination
	// start by computing the delta
	m_delta = m_destination - position;
	// get the signs of the delta
	m_delta_sign = sign_of( m_delta );
	// change the delta to an absolute value
	m_delta = abs( m_delta );
	// compute the divisor as the largest of abs(row) or abs(column)
	m_threshold = m_delta.column;
	if (m_threshold < m_delta.row)
		m_threshold = m_delta.row;
	// delta is in subcell units.  Compute the distance in whole cell units.
	distance = m_threshold >> k_battlefield_subcell_shift;
	// start our initial destination as the final destination
	m_waypoint = m_destination;
	waypoint_position = distance;
	// we are moving a full cell with each step, so multiply the delta by 16
	m_delta <<= k_battlefield_subcell_shift;
	// save our initial position
	points.push_back( position );
	m_obstacle = 0;
	for (i = 0; i < distance; i++)
	{
		// move forward one whole cell
		position += advance();
		// save our new position
		points.push_back( position );
		// compute the height of the terrain in the new position
		height = m_battlefield.compute_terrain_height_under_object( m_creature, position );
		// if the new height is greater than our projected height,
		// set a new waypoint
		if (height > position.height)
		{
			// compute the overall change in height if we stay on this slope until
			// we reach the destination.
			m_delta.height		= ((height - start.height) * m_threshold) / (i + 1);
			// change sign and absolute value variables
			m_delta_sign.height = sign_of( m_delta.height );
			m_delta.height		= abs( m_delta.height );
			// set our new height, and save this as our new waypoint
			position.height		= height;
			m_waypoint = position;
			waypoint_position = i + 1;
			m_obstacle = 0;
		}
		// check if we are over an obstacle
		obstacle = get_tallest_object( position >> k_battlefield_subcell_shift );
		// if we have changed obstacles, or this is the last point, compute
		// how high we must fly to clear the center.
		if (obstacle != last_obstacle || i + 1 == distance)
		{
			// if we are transitioning from an obstacle, rather than an empty space
			t_object_list::const_iterator index = m_passed_obstacles.begin();
			t_object_list::const_iterator end   = m_passed_obstacles.end();

			for (; index != end; index++)
				if (last_obstacle == index->get())
					break;
			if (last_obstacle != 0 && index == end)
			{
				// compute center point as the average of start & stop.  stop is either
				// last point (i), or this point (i+1), if this is the last point
				if (obstacle != last_obstacle)
					obstacle_start = (obstacle_start + i) >> 1;
				else
					obstacle_start = (obstacle_start + i + 1) >> 1;
				// get the center point
				obstacle_center = points[obstacle_start];
				// compute the height as the terrain height + object height
				height = last_obstacle->get_position().height + last_obstacle->get_height();
				obstacle_center.height = height;
				// compute how high we would be at the current waypoint if we barely skim
				// the top of the obstacle
				height = ((height - start.height) * waypoint_position)/ obstacle_start;
				height += start.height;
				if (height > m_waypoint.height)
				{
					m_delta.height = ((obstacle_center.height - start.height) * m_threshold)
									  / obstacle_start;
					// set our new height
					position.height = (m_delta.height * (i + 1)) / m_threshold;
					position.height += start.height;
					// change sign and absolute value variables
					m_delta_sign.height = sign_of( m_delta.height );
					m_delta.height		= abs( m_delta.height );
					// save this as our new waypoint
					m_waypoint = obstacle_center;
					waypoint_position = obstacle_start;
					m_obstacle = last_obstacle;
				}
			}
			last_obstacle = obstacle;
			obstacle_start = i + 1;
		}
	}
}

// -----------------------------------------------------------------------
// fly to a location
// now that we have a waypoint, we need to compute how to get there.
// -----------------------------------------------------------------------
void t_play_combat_flight::compute_distances( int frame )
{
	// compute the # of walk cycle animations we will show.
	int						 prewalk_distance  = 0;
	int						 postwalk_distance = 0;
	int						 walk_distance     = m_creature.get_walk_distance();
	int						 animation_distance;
	int						 distance;
	int						 walk_cycles;
	int						 waypoint_distance;
	int                      overall_distance;
	t_combat_actor_action_id action = m_creature.get_current_action();
	int                      frame_count = m_creature.get_frame_count();

	if (m_creature.is_active( k_spell_flight ) && walk_distance < 80)
		walk_distance = 80;
	m_delta           = m_destination - m_creature.get_position();
	overall_distance  = sqrt( (double)(m_delta.row * m_delta.row + m_delta.column * m_delta.column));
	m_delta           = m_waypoint - m_creature.get_position();
	waypoint_distance = sqrt( (double)(m_delta.row * m_delta.row + m_delta.column * m_delta.column));

	if (action == k_combat_actor_action_prewalk && frame < frame_count)
		prewalk_distance = (frame_count - frame) * m_creature.get_prewalk_distance() 
		                   / frame_count;
	postwalk_distance = m_creature.get_postwalk_distance();
	if (action == k_combat_actor_action_postwalk)
	{
		walk_cycles = 0;
		postwalk_distance = (frame_count - frame) * postwalk_distance / frame_count;
		animation_distance = postwalk_distance;
	}
	else
	{
		int remainder;

		distance = overall_distance - prewalk_distance - postwalk_distance;
		if (action == k_combat_actor_action_walk)
			distance += frame * walk_distance / frame_count;
		walk_cycles = distance / walk_distance;
		if (walk_cycles < 0)
			walk_cycles = 0;
		remainder = distance - walk_cycles * walk_distance;
		if ((prewalk_distance + postwalk_distance) * 2 < remainder)
			walk_cycles++;
		animation_distance = (walk_distance * walk_cycles) + prewalk_distance 
			                 + postwalk_distance;
		if (action == k_combat_actor_action_walk && walk_cycles > 0)
			animation_distance -= frame * walk_distance / frame_count;
	}
	// if this is the pre-walk, shorten the distance and the waypoint
	if (action == k_combat_actor_action_prewalk && frame < frame_count)
	{
		// calculate distance for remaining prewalk frames
		distance = prewalk_distance * overall_distance / animation_distance;
		m_frames = frame_count - frame;
	}
	else if (walk_cycles > 0)
	{   // compute the distance for remaining walk frames
		distance = walk_cycles * walk_distance;
		if (action == k_combat_actor_action_walk)
		{
			distance -= frame * walk_distance / frame_count;
		}
		else
		{
			m_battlefield.set_current_action( m_creature, k_combat_actor_action_walk );
			frame_count = m_creature.get_frame_count();
			frame = 0;
		}
		distance = distance * overall_distance / animation_distance;
		m_frames = walk_cycles * frame_count - frame;
	}
	else
	{
		if (action == k_combat_actor_action_postwalk)
		{
			m_frames = frame_count - frame;
		}
		else
		{
			m_battlefield.set_current_action( m_creature, k_combat_actor_action_postwalk );
			m_frames = m_creature.get_frame_count();
		}
		distance = postwalk_distance;
		if (m_waypoint == m_destination)
			distance = waypoint_distance;
		if (m_frames < 1)
			m_frames = 1;
	}
	if (distance < 0)
		distance = 0;
	// if distance is less than waypoint distance, recalculate the waypoint
	if (distance < waypoint_distance)
	{
		if (distance > 0)
			m_delta = m_delta * distance / waypoint_distance;
		m_waypoint = m_delta + m_creature.get_position();
	}
	else
	{
		if (m_obstacle != 0)
			m_passed_obstacles.push_back( m_obstacle );
		if (m_waypoint != m_destination && distance > waypoint_distance)
	// if distance is >= waypoint distance, reduce the # of frames.
			m_frames = (m_frames * waypoint_distance + distance - 1) / distance;
	}
	m_threshold  = m_frames;
	m_delta_sign = sign_of( m_delta );
	m_delta      = abs( m_delta );
	set_delay( 100000 / (m_creature.get_frames_per_second() * get_combat_animation_speed()));
	m_sum        = t_map_point_3d(0,0,0);
}


// -----------------------------------------------------------------------
// fly to a location
// find the tallest object we will intersect if we move to a location
// -----------------------------------------------------------------------
t_abstract_combat_object* t_play_combat_flight::get_tallest_object( t_map_point_2d const& point ) const
{
	t_combat_footprint const& footprint = m_creature.get_footprint();
	int                       footprint_size = footprint.get_size();
	t_map_point_2d            offset;
	t_map_point_2d            new_point;
	int const*				  ptr = footprint.get();
	t_battlefield_cell const* cell;
	t_abstract_combat_object* result = 0;
	int                       tallest;
	int                       height;

	for (offset.row = 0; offset.row < footprint_size; offset.row++)
	{
		offset.column = 0;
		for (offset.column = 0; offset.column < footprint_size; offset.column++)
		{
			if (!(*(ptr++)))
				continue;
			new_point = point + offset;
			assert( m_battlefield.is_valid( new_point ) );
			cell = &m_battlefield.get_cell( new_point );

			t_combat_object_list::const_iterator index = cell->get_objects().begin();
			t_combat_object_list::const_iterator end   = cell->get_objects().end();

			for (; index != end; index++)
			{
				if (index->get() == &m_creature)
					continue;
				if (index->get()->is_underlay())
					continue;
				assert( index->get() != m_spell_effect);
				height = index->get()->get_position().height + index->get()->get_height();
				if (result != 0 && height < tallest)
					continue;
				result = index->get();
				tallest = height;
			}
		}
	}
	return result;
}
